package com.jack.lib.net.download

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.coroutineScope
import com.blankj.utilcode.util.FileUtils
import com.blankj.utilcode.util.RegexUtils
import com.jack.lib.core.file.JFileManager
import com.jack.lib.core.utils.JLog
import com.jack.lib.net.JNetManager
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.*
import retrofit2.HttpException
import java.io.*
import java.lang.RuntimeException
import java.util.concurrent.TimeUnit

/**
 * author : chong.huang
 * create : 2022/1/16
 * desc   :
 */
class Downloader(
    private val lifecycle: Lifecycle,
    val url: String,
    val path: String = JFileManager.download,
    val onSuccess: (File) -> Unit = { },
    val onProcess: (Float) -> Unit = { },
    val onFailed: (Throwable) -> Unit = { }
) {

    val TAG = "Downloader"

    private val call: Call by lazy {
        val okHttpClient = OkHttpClient().newBuilder()
            .connectTimeout(20, TimeUnit.MINUTES)
            .readTimeout(20, TimeUnit.MINUTES)
            .writeTimeout(20, TimeUnit.MINUTES)
            .build()

        val request: Request = Request.Builder()
            .url(url)
            .build()

        okHttpClient.newCall(request)
    }

    private val handler = CoroutineExceptionHandler { _, exception ->
        JLog.e(TAG, exception.message)
        onFailed.invoke(exception)
    }


    init {

        lifecycle.addObserver(object : LifecycleEventObserver {
            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    cancel()
                }
            }
        })

        lifecycle.coroutineScope.launch(handler) {

            if (!RegexUtils.isURL(url)) {
                JLog.e(TAG, "onFailed", "url 无效")
                return@launch onFailed.invoke(RuntimeException("url 无效"))
            }

            withContext(Dispatchers.IO) {
                kotlin.runCatching {
                    val execute = call.execute()

                    if (execute.isSuccessful) {
                        if (execute.body == null) {
                            JLog.e(TAG, "request.body == null")
                        }
                        lifecycle.coroutineScope.launch(Dispatchers.Main) {
                            val file = File(path + FileUtils.getFileName(url))
                            FileUtils.createOrExistsDir(file.parentFile)
                            writeFile(file, execute.body!!)
                        }
                    }else {
                        JLog.e(TAG, "${execute.code} ${execute.message}")
                        onFailed.invoke(RuntimeException("${execute.code} ${execute.message}"))
                    }

                }.onFailure {
                    lifecycle.coroutineScope.launch(Dispatchers.Main) {
                        JLog.i(TAG, "onFailed", it.message)
                        onFailed.invoke(it)
                    }
                }

            }
        }
    }


    private suspend fun writeFile(file: File, body: ResponseBody) {
        withContext(Dispatchers.IO) {
            kotlin.runCatching {
                var outputStream: OutputStream? = null
                val inputStream = body.byteStream()
                val contentLength = body.contentLength()
                var currentLength: Long = 0

                if (file.exists() && file.length() == contentLength) {
                    if (withContext(Dispatchers.Main) {
                            JLog.i(TAG, "onSuccess", "exists", file.absolutePath)
                            onSuccess(file)
                            cancel()
                            true
                    }) {
                        return@withContext
                    }
                }

                withContext(Dispatchers.Main) {
                    onProcess(0f)
                }
                try {
                    outputStream = FileOutputStream(file)
                    var len: Int
                    val buffer = ByteArray(1024 * 10)
                    while (inputStream.read(buffer).also { len = it } != -1) {
                        outputStream.write(buffer, 0, len)
                        currentLength += len
                        withContext(Dispatchers.Main) {
                            onProcess(currentLength.toFloat() / contentLength.toFloat())
                        }

                    }
                    JLog.i(TAG, "onSuccess", file.absolutePath)
                    withContext(Dispatchers.Main) {
                        onProcess(1F)
                        onSuccess(file)
                    }

                } catch (e: FileNotFoundException) {
                    e.printStackTrace()
                    JLog.e(TAG, JLog.e(e.message))
                    withContext(Dispatchers.Main) {
                        onFailed(e)
                    }
                } catch (e: IOException) {
                    e.printStackTrace()
                    JLog.e(TAG, JLog.e(e.message))
                    withContext(Dispatchers.Main) {
                        onFailed(e)
                    }
                } finally {
                    try {
                        inputStream.close()
                        outputStream?.close()
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }
                }
            }.onFailure {
                JLog.e(TAG, JLog.e(it.message))
                withContext(Dispatchers.Main) {
                    onFailed(it)
                }
            }
        }
    }

    /**
     * author  : chong.huang
     * time    : 2022/1/16 16:01
     * desc    : 取消下载
     */
    fun cancel() {
        kotlin.runCatching {
            if (!call.isCanceled()) {
                call.cancel()
            }
        }.onFailure {
            JLog.e(TAG, "onFailed", it.message)
            onFailed.invoke(it)
        }

    }
}